#include "Kernel.h"
#include "Hal/IHardware.h"
#include "Hal/x86/Hardware.h"
#include "Hal/x86/CPU.h"
#include "Hal/x86/IDT.h"
#include "Drivers/Console.h"

using namespace x86;

enum
{
	IDT_MAX_ENTRIES	= 256,

	ISR_CALL_GATE = 0x04,
	ISR_TASK_GATE = 0x05,
	ISR_INTERRUPT_GATE = 0x06,
	ISR_TRAP_GATE = 0x07,
	ISR_16BIT = 0x00,
	ISR_32BIT = 0x08,
	ISR_RING_0 = 0x00,
	ISR_RING_1 = 0x20,
	ISR_RING_2 = 0x40,
	ISR_RING_3 = 0x60,
	ISR_PRESENT = 0x80,

	PIC_MASTER_COMMAND_PORT = 0x0020,
	PIC_SLAVE_COMMAND_PORT = 0x00A0,
	PIC_MASTER_DATA_PORT = 0x0021,
	PIC_SLAVE_DATA_PORT = 0x00A1,
	PIC_MASTER_MASK = 0xFB,
	PIC_SLAVE_MASK = 0xFF,
	PIC_MASTER_INTERRUPT = INT_IRQ_0,
	PIC_SLAVE_INTERRUPT = INT_IRQ_8,
	PIC_MASTER_ICW1 = 0x11,
	PIC_MASTER_ICW2 = 0x04,
	PIC_MASTER_ICW3 = 0x01,
	PIC_MASTER_ACK = 0x20,
	PIC_SLAVE_ICW1 = 0x11,
	PIC_SLAVE_ICW2 = 0x02,
	PIC_SLAVE_ICW3 = 0x01,
	PIC_SLAVE_ACK = 0x20,
};

struct IDT_Descriptor
{
	uint16	OffsetLow;
	uint16	Selector;
	uint8	__unused;
	uint8	Flags;
	uint16	OffsetHigh;
} _PACKED;

typedef struct IDT_Descriptor IDT_Descriptor;

extern "C"
{
	void LoadIDT( void );
	void ISR_0( void );
	void ISR_1( void );
	void IDT_CallHandler( Registers * registers );

	DescriptorTable_Pointer IDT_Pointer;
	IDT_Descriptor IDT_Entries[IDT_MAX_ENTRIES];
	InterruptHandler ISR_Entries[IDT_MAX_ENTRIES];
}

uint8 PIC_MasterPorts;
uint8 PIC_SlavePorts;


void IDT_CallHandler( Registers * regs )
{
	ASSERT( regs!= NULL );
	ASSERT( regs->Interrupt >= 0 && regs->Interrupt < IDT_MAX_ENTRIES );

	IDT_TRACE( "Interrupt %2x received.\n", regs->Interrupt );

	InterruptHandler handler = ISR_Entries[regs->Interrupt];

	static TaskContext context;
	Memory_Copy( &context.fRegisters, regs, sizeof(Registers) );

	if( handler != NULL )
		handler( &context );
	else
		IDT::DefaultHandler( &context );

	if( regs->Interrupt >= INT_IRQ_0 && regs->Interrupt <= INT_IRQ_15 )
	{
		if( regs->Interrupt >= INT_IRQ_8 && regs->Interrupt <= INT_IRQ_15 )
			IO_Out8( PIC_SLAVE_COMMAND_PORT, PIC_SLAVE_ACK );

		IO_Out8( PIC_MASTER_COMMAND_PORT, PIC_MASTER_ACK );
		IO_Delay();

		IDT_TRACE( "Sent IRQ ACK message...\n" );
	}
}


Result IDT::Initialize( void )
{
	IDT_TRACE( "Initializing...\n" );

	IDT_Pointer.Size = (uint16)( sizeof(IDT_Descriptor) * IDT_MAX_ENTRIES - 1 );
	IDT_Pointer.Address = (uint32)&IDT_Entries[0];

	for( uint32 i = 0; i < IDT_MAX_ENTRIES; i++ )
	{
		InitializeEntry( i, (uint32)&ISR_0 + i * ( (uint32)&ISR_1 - (uint32)&ISR_0 ),
				ISR_PRESENT | ISR_INTERRUPT_GATE | ISR_RING_0 | ISR_32BIT );
		ISR_Entries[i] = &IDT::DefaultHandler;
	}

	asm volatile ( "lidt (%0)" : : "r" ((uint32)&IDT_Pointer) );

	PIC_MasterPorts = 0xFF;
	PIC_SlavePorts = 0xFF;

	// update the PIC and disable all interrupts
	IO_Out8( PIC_MASTER_COMMAND_PORT, PIC_MASTER_ICW1 );
	IO_Out8( PIC_SLAVE_COMMAND_PORT, PIC_SLAVE_ICW1 );
	IO_Out8( PIC_MASTER_DATA_PORT, PIC_MASTER_INTERRUPT );
	IO_Out8( PIC_SLAVE_DATA_PORT, PIC_SLAVE_INTERRUPT );
	IO_Out8( PIC_MASTER_DATA_PORT, PIC_MASTER_ICW2 );
	IO_Out8( PIC_SLAVE_DATA_PORT, PIC_SLAVE_ICW2 );
	IO_Out8( PIC_MASTER_DATA_PORT, PIC_MASTER_ICW3 );
	IO_Out8( PIC_SLAVE_DATA_PORT, PIC_SLAVE_ICW3 );
	IO_Out8( PIC_MASTER_DATA_PORT, PIC_MasterPorts );
	IO_Out8( PIC_SLAVE_DATA_PORT, PIC_SlavePorts );
	IO_Delay();

	return RESULT_OK;
}


Result IDT::RegisterHandler( int interrupt, InterruptHandler handler )
{
	if( handler == NULL )
		ISR_Entries[interrupt] = &IDT::DefaultHandler;
	else
		ISR_Entries[interrupt] = handler;

	// if this isn't an interrupt, we're done
	if( interrupt < INT_IRQ_0 || interrupt > INT_IRQ_15 )
		return RESULT_OK;

	if( interrupt >= INT_IRQ_0 && interrupt <= INT_IRQ_7 )
	{
		uint8 mask = 1 << ( interrupt - INT_IRQ_0 );

		if( handler != NULL )
			PIC_MasterPorts &= ~mask;
		else
			PIC_MasterPorts |= mask;
	}
	else if( interrupt >= INT_IRQ_8 && interrupt <= INT_IRQ_15 )
	{
		uint8 mask = 1 << ( interrupt - INT_IRQ_8 );

		if( handler != NULL )
			PIC_SlavePorts &= ~mask;
		else
			PIC_SlavePorts |= mask;
	}

	// TEMP: force IRQ 0 - 2 to be enabled
	IO_Out8( PIC_MASTER_DATA_PORT, PIC_MasterPorts );
	IO_Out8( PIC_SLAVE_DATA_PORT, PIC_SlavePorts );
	IO_Delay();

	return RESULT_OK;
}


void IDT::InitializeEntry( int index, uint32 offset, uint8 flags )
{
	IDT_Descriptor * entry = &IDT_Entries[index];

	entry->OffsetLow = (uint16)( offset & 0xFFFF );
	entry->OffsetHigh = (uint16)( ( offset >> 16 ) & 0xFFFF );
	entry->Selector = 0x08;
	entry->Flags = (uint8)flags;
	entry->__unused = 0;
}


void IDT::DefaultHandler( ITaskContext * context )
{
	ASSERT( context != NULL );

	Registers * regs = &((TaskContext *)context)->fRegisters;

	if( regs->Interrupt >= INT_IRQ_0 && regs->Interrupt <= INT_IRQ_15 )
	{
		IDT_TRACE( "Unhandled IRQ %02x, ignored.\n", regs->Interrupt - INT_IRQ_0 );
		return;
	}

	asm( "cli" );

	sConsole.SetForeground( Console::ColorWhite );
	sConsole.SetBackground( Console::ColorBlue );
	sConsole.Clear();

	static char * exceptionStrings[] =
	{
		"Divide Error",
		"Debugger Exceptions",
		"Reserved",
		"Breakpoint",
		"Overflow",
		"Bounds Check",
		"Invalid Opcode",
		"Coprocessor Not Available",
		"Double Fault",
		"Coprocessor Segment Overrun",
		"Invalid TSS",
		"Segment Not Present",
		"Stack Exception",
		"General Protection Fault",
		"Page Fault",
		"Reserved",
		"Coprocessor Error",
		"Unknown"
	};

	IDT_TRACE( "Unhandled Interrupt:\n" );
	sHardware->DumpContext( context, &sDebugger );

	sConsole.SetPosition( 2, 1 );
	sConsole.Write( "Mint - Unhandled Interrupt:\n" );

	char * exception;
	if( regs->Interrupt == 0xFF )
		exception = "Fake Interrupt";
	else
		exception = exceptionStrings[ MIN( regs->Interrupt, ARRAY_SIZE(exceptionStrings) ) ];

	sConsole.SetPosition( 2, 3 );
	sConsole.Write( exception );

	sConsole.SetPosition( 2, 6 );
	sConsole.Write( "Context:\n" );
	sHardware->DumpContext( context, &sConsole );

	sConsole.SetPosition( 2, sConsole.GetHeight() - 2 );
	sConsole.Write( "System halting...\n" );

	for(;;)
		;
}
